package eval

import (
	"errors"
	"fmt"
	"showen/ast"
	"showen/num"
	"showen/obj"
	"showen/tok"
	"strings"
)

// TailCallResult represents a tail call that needs to be executed
type TailCallResult struct {
	Proc obj.Object
	Args []obj.Object
	Env  *obj.Environment
}

func (t *TailCallResult) Type() obj.ObjectType {
	return -1 // Special type for tail call result
}

func (t *TailCallResult) Inspect() string {
	return "tail-call-result"
}

// EvalExpression evaluates an expression and returns the result
func EvalExpression(exp ast.Exp, env *obj.Environment) (obj.Object, error) {
	return evalExpressionWithTailCall(exp, env, false)
}

// EvalExpressionTrampoline is the main evaluation loop that handles tail calls
func EvalExpressionTrampoline(exp ast.Exp, env *obj.Environment) (obj.Object, error) {
	currentExp := exp
	currentEnv := env

	for {
		result, err := evalExpressionWithTailCall(currentExp, currentEnv, true)
		if err != nil {
			return nil, err
		}

		// Check if we got a tail call result
		if tailCall, ok := result.(*TailCallResult); ok {
			// Apply the procedure directly using applyObjectProcedure
			result, err := applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
			if err != nil {
				return nil, err
			}
			return result, nil
		} else {
			// Not a tail call, return the result
			return result, nil
		}
	}
}

// evalExpressionWithTailCall is the internal evaluation function that supports tail call optimization
func evalExpressionWithTailCall(exp ast.Exp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	switch exp := exp.(type) {
	case *ast.IdentifierExp:
		return evalIdentifier(exp, env)
	case *ast.BooleanExp:
		return evalBoolean(exp)
	case *ast.NumberExp:
		return evalNumber(exp)
	case *ast.StringExp:
		return evalString(exp)
	case *ast.CharacterExp:
		return evalCharacter(exp)
	case *ast.QuoteExp:
		return quoteExpressionToObject(exp.Value)
	case *ast.QuasiquoteExp:
		return evalQuasiquote(exp, env)
	case *ast.UnquoteExp:
		return evalUnquote(exp, env)
	case *ast.UnquoteSplicingExp:
		return evalUnquoteSplicing(exp, env)
	case *ast.ListExp:
		return evalListExpressionWithTailCall(exp, env, inTailPosition)
	case *ast.VectorExp:
		// Evaluate each element of the vector
		elements := make([]obj.Object, len(exp.Exps))
		for i, subExp := range exp.Exps {
			elem, err := evalExpressionWithTailCall(subExp, env, false) // Vector elements are not in tail position
			if err != nil {
				return nil, err
			}
			elements[i] = elem
		}
		return &obj.VectorObject{Elements: elements}, nil
	default:
		return nil, fmt.Errorf("unknown expression type: %T", exp)
	}
}

// Modified applyObjectProcedure to support tail calls
func applyObjectProcedure(procObj obj.Object, args []obj.Object, env *obj.Environment) (obj.Object, error) {
	switch proc := procObj.(type) {
	case *obj.Builtin:
		result := proc.Fn(args, env)
		if result.Type() == obj.ERROR_OBJ {
			return nil, errors.New(result.Inspect())
		}
		return result, nil
	case *obj.ContinuationObject:
		// Handle continuation objects specially
		result, err := proc.Invoke(args)
		if err != nil {
			// If it's a ContinuationResultError, we propagate it up
			if _, ok := err.(*obj.ContinuationResultError); ok {
				return nil, err
			}
			return nil, err
		}
		return result, nil
	case *obj.Procedure:
		// Create a new environment for the procedure call
		newEnv := obj.NewEnclosedEnvironment(proc.Env)

		// Bind parameters to arguments
		if len(proc.Params) != len(args) {
			return nil, fmt.Errorf("wrong number of arguments to %s: expected %d, got %d",
				proc.Name, len(proc.Params), len(args))
		}

		for i, param := range proc.Params {
			newEnv.Set(param, args[i])
		}

		// Evaluate the procedure body
		var result obj.Object
		var err error
		for i, expr := range proc.Body {
			// Only the last expression is in tail position
			isTailPosition := (i == len(proc.Body)-1)
			if isTailPosition {
				// For the last expression, we might be able to do a tail call optimization
				// But for now, we'll evaluate it normally
				result, err = evalExpressionWithTailCall(expr, newEnv, false)
				// Check if we got a tail call result - this should not happen since we're passing false
				if tailCall, ok := result.(*TailCallResult); ok {
					// This should not happen, but if it does, apply it directly
					result, err = applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
					if err != nil {
						return nil, err
					}
				}
			} else {
				result, err = evalExpressionWithTailCall(expr, newEnv, false)
				// Check if we got a tail call result - this should not happen since we're passing false
				if tailCall, ok := result.(*TailCallResult); ok {
					// This should not happen, but if it does, apply it directly
					result, err = applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
					if err != nil {
						return nil, err
					}
				}
			}
			if err != nil {
				// If it's a ContinuationResultError, we propagate it up
				if _, ok := err.(*obj.ContinuationResultError); ok {
					return nil, err
				}
				return nil, err
			}
		}

		return result, nil
	case *obj.Lambda:
		// Create a new environment for the lambda call
		newEnv := obj.NewEnclosedEnvironment(proc.Env)

		// Handle parameters - check if we have a rest parameter
		var restParam string
		actualParams := proc.Parameters

		// Check if the last parameter is a rest parameter (marked with &rest:)
		if len(proc.Parameters) > 0 && strings.HasPrefix(proc.Parameters[len(proc.Parameters)-1], "&rest:") {
			// Extract the rest parameter name
			restParam = proc.Parameters[len(proc.Parameters)-1][6:] // Remove "&rest:" prefix
			// Remove the rest parameter marker from actual parameters
			actualParams = proc.Parameters[:len(proc.Parameters)-1]
		}

		// Bind regular parameters to arguments
		if len(actualParams) > len(args) {
			return nil, fmt.Errorf("wrong number of arguments: expected at least %d, got %d",
				len(actualParams), len(args))
		}

		// Bind regular parameters
		for i, param := range actualParams {
			newEnv.Set(param, args[i])
		}

		// Handle rest parameter if we have one
		if restParam != "" {
			// Create a list of remaining arguments
			var restArgs []obj.Object
			if len(args) > len(actualParams) {
				restArgs = args[len(actualParams):]
			} else {
				// No extra arguments, rest parameter gets empty list
				restArgs = []obj.Object{}
			}

			// Convert restArgs to a proper list
			var restList obj.Object = obj.EMPTY_LIST
			for i := len(restArgs) - 1; i >= 0; i-- {
				restList = &obj.PairObject{
					Car: restArgs[i],
					Cdr: restList,
				}
			}

			// Bind the rest parameter to the list of remaining arguments
			newEnv.Set(restParam, restList)
		} else if len(actualParams) != len(args) {
			// No rest parameter, so exact match is required
			return nil, fmt.Errorf("wrong number of arguments: expected %d, got %d",
				len(actualParams), len(args))
		}

		// Evaluate the lambda body with tail call support
		result, err := evalExpressionWithTailCall(proc.Body, newEnv, false)
		// Check if we got a tail call result - this should not happen since we're passing false
		if tailCall, ok := result.(*TailCallResult); ok {
			// This should not happen, but if it does, apply it directly
			result, err = applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
			if err != nil {
				return nil, err
			}
		}
		// If it's a ContinuationResultError, we propagate it up
		if err != nil {
			if _, ok := err.(*obj.ContinuationResultError); ok {
				return nil, err
			}
			return nil, err // Propagate other errors properly
		}
		return result, err
	default:
		return nil, fmt.Errorf("attempt to apply non-procedure: %s", procObj.Inspect())
	}
}

func quoteExpressionToObject(exp ast.Exp) (obj.Object, error) {
	switch e := exp.(type) {
	case *ast.BooleanExp:
		return &obj.BooleanObject{Value: e.Token.Literal.(bool)}, nil
	case *ast.IdentifierExp:
		return &obj.SymbolObject{Value: e.Token.Lexeme}, nil
	case *ast.NumberExp:
		return &obj.NumberObject{Value: e.Token.Literal.(num.Number)}, nil
	case *ast.StringExp:
		return &obj.StringObject{Value: e.Token.Literal.(string)}, nil
	case *ast.CharacterExp:
		return &obj.CharacterObject{Value: rune(e.Token.Literal.(byte))}, nil
	case *ast.QuoteExp:
		// For nested quotes, we need to create a list with 'quote as the first element
		// and the inner quoted expression as the second element
		quoteSymbol := &obj.SymbolObject{Value: "quote"}
		quotedValue, err := quoteExpressionToObject(e.Value)
		if err != nil {
			return nil, err
		}

		// Create a pair representing (quote ...)
		pair := &obj.PairObject{}
		pair.Car = quoteSymbol
		pair.Cdr = &obj.PairObject{
			Car: quotedValue,
			Cdr: obj.EMPTY_LIST,
		}

		return pair, nil
	case *ast.ListExp:
		// Handle empty list as NULL_OBJECT
		if len(e.Exps) == 0 {
			return obj.EMPTY_LIST, nil
		}
		root := new(obj.PairObject)
		current := root
		elem, err := quoteExpressionToObject(e.Exps[0])
		if err != nil {
			return nil, err
		}
		current.Car = elem
		improper := false
		for i, subExp := range e.Exps[1:] {
			elem, err := quoteExpressionToObject(subExp)
			if err != nil {
				return nil, err
			}
			if elem.Type() == obj.SYMBOL_OBJ && elem.Inspect() == "." {
				improper = true
				continue
			} else {
				if !improper {
					o := new(obj.PairObject)
					o.Car = elem
					current.Cdr = o
					current = o
				} else {
					// Handle improper list - set the final cdr
					current.Cdr = elem
					//fmt.Println("last:", i)
					if i != len(e.Exps[1:])-1 {
						return nil, fmt.Errorf("the data behind . must be the last element")
					}
				}
			}
		}
		// For proper lists, the last cdr should be nil
		if !improper {
			current.Cdr = obj.EMPTY_LIST
		}
		return root, nil
	case *ast.VectorExp:
		elements := make([]obj.Object, len(e.Exps))
		for i, subExp := range e.Exps {
			elem, err := quoteExpressionToObject(subExp)
			if err != nil {
				return nil, err
			}
			elements[i] = elem
		}
		return &obj.VectorObject{Elements: elements}, nil
	default:
		return obj.NULL_OBJECT, nil
	}
}

// isTruthy determines if an object is truthy in Scheme
// In Scheme, only #f is false, everything else is true
func isTruthy(o obj.Object) bool {
	if o.Type() == obj.BOOLEAN_OBJ {
		return o.(*obj.BooleanObject).Value
	}
	return true
}

func evalBoolean(exp *ast.BooleanExp) (obj.Object, error) {
	return &obj.BooleanObject{Value: exp.Token.Literal.(bool)}, nil
}

func evalNumber(exp *ast.NumberExp) (obj.Object, error) {
	return &obj.NumberObject{Value: exp.Token.Literal.(num.Number)}, nil
}

func evalString(exp *ast.StringExp) (obj.Object, error) {
	return &obj.StringObject{Value: exp.Token.Literal.(string)}, nil
}

func evalCharacter(exp *ast.CharacterExp) (obj.Object, error) {
	return &obj.CharacterObject{Value: rune(exp.Token.Literal.(byte))}, nil
}

func evalIdentifier(exp *ast.IdentifierExp, env *obj.Environment) (obj.Object, error) {
	if val, ok := env.Get(exp.Token.Literal.(string)); ok {
		return val, nil
	}

	// We don't handle builtins here, they should be in the environment
	return nil, fmt.Errorf("identifier not found: %s", exp.Token.Literal)
}

// evalListExpressionWithTailCall evaluates a list expression with tail call optimization support
func evalListExpressionWithTailCall(expr *ast.ListExp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	if len(expr.Exps) == 0 {
		return nil, fmt.Errorf("list cannot be empty")
	}
	if id, ok := expr.Exps[0].(*ast.IdentifierExp); ok {
		switch id.Token.Lexeme {
		case "delay":
			return applyDelay(expr.Exps[1:], env)
		case "force":
			return applyForce(expr.Exps[1:], env)
		case "and":
			return applyAndWithTailCall(expr.Exps[1:], env)
		case "begin":
			return applyBeginWithTailCall(expr.Exps[1:], env, inTailPosition)
		case "case":
			return applyCaseWithTailCall(expr.Exps[1:], env)
		case "cond":
			return applyCondWithTailCall(expr.Exps[1:], env, inTailPosition)
		case "define":
			return applyDefine(expr.Exps[1:], env)
		case "define-syntax":
			return applyDefineSyntax(expr.Exps[1:], env)
		case "do":
			return applyDo(expr.Exps[1:], env)
		case "if":
			return applyIfWithTailCall(expr.Exps[1:], env, inTailPosition)
		case "lambda":
			return evalLambda(expr.Exps[1:], env)
		case "let":
			return applyLetWithTailCall(expr.Exps[1:], env, inTailPosition)
		case "letrec":
			return applyLetRecWithTailCall(expr.Exps[1:], env, inTailPosition)
		case "let*":
			return applyLetStarWithTailCall(expr.Exps[1:], env, inTailPosition)
		case "or":
			return applyOrWithTailCall(expr.Exps[1:], env)
		case "set!":
			return applySetBang(expr.Exps[1:], env)
		case "quasiquote":
			return applyQuasiquote(expr.Exps[1:], env)
		case "unquote":
			return applyUnquote(expr.Exps[1:], env)
		case "unquote-splicing":
			return applyUnquoteSplicing(expr.Exps[1:], env)
		default:
			// Before normal procedure application, check whether the operator is a macro
			fname := id.Token.Lexeme
			if f, ok := env.Get(fname); ok {
				if f.Type() == obj.MACRO_OBJ {
					// Expand macro using its syntax-rules clauses
					macro := f.(*obj.MacroObject)
					ae, err := expandMacro(macro, expr)
					if err != nil {
						return nil, err
					}
					return evalExpressionWithTailCall(ae, env, inTailPosition)
				}
			}
			return evalProcedureWithTailCall(expr, env, inTailPosition)
		}
	}
	return evalApplicationWithTailCall(expr, env, inTailPosition)
}

func evalLambda(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	if len(args) < 2 {
		return nil, errors.New("lambda requires at least parameters and body")
	}

	// Handle parameter list - can be either:
	// 1. A proper list of identifiers: (param1 param2 ...)
	// 2. A single identifier that captures all arguments as a list: args
	var params []string
	var restParam string

	// Check if the first argument is a single identifier (for rest parameters)
	if identExp, ok := args[0].(*ast.IdentifierExp); ok {
		// This is the case where we capture all arguments as a list
		restParam = identExp.Token.Lexeme
		params = []string{} // No regular parameters
	} else if paramsList, ok := args[0].(*ast.ListExp); ok {
		// This is the normal case with a list of parameters
		// Extract parameter names
		for _, paramExpr := range paramsList.Exps {
			paramIdent, ok := paramExpr.(*ast.IdentifierExp)
			if !ok {
				return nil, errors.New("lambda parameters must be identifiers")
			}
			params = append(params, paramIdent.Token.Lexeme)
		}
	} else {
		return nil, errors.New("lambda parameters must be a list or identifier")
	}

	// Create a lambda object
	// For the body, we need to handle multiple expressions properly
	// If there's only one expression, use it directly
	// If there are multiple expressions, we need to wrap them in a begin expression
	var body ast.Exp
	if len(args[1:]) == 1 {
		body = args[1]
	} else {
		// Create a begin expression to evaluate multiple expressions in sequence
		beginExpr := &ast.ListExp{
			Exps: append([]ast.Exp{&ast.IdentifierExp{
				Token: tok.Token{Type: tok.IDENTIFIER, Lexeme: "begin"},
			}}, args[1:]...),
		}
		body = beginExpr
	}

	lambda := &obj.Lambda{
		Parameters: params,
		Body:       body,
		Env:        env,
	}

	// If we have a rest parameter, we need to handle it specially
	// We'll use a special naming convention to indicate this
	if restParam != "" {
		// Add a special marker to indicate we have a rest parameter
		lambda.Parameters = append(lambda.Parameters, "&rest:"+restParam)
	}

	return lambda, nil
}

// evalProcedureWithTailCall evaluates a procedure call with tail call optimization
func evalProcedureWithTailCall(expr *ast.ListExp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	fname := expr.Exps[0].(*ast.IdentifierExp).Token.Lexeme
	f, ok := env.Get(fname)
	if !ok {
		return nil, fmt.Errorf("undefined: %s", fname)
	}

	// Evaluate arguments
	args := make([]obj.Object, len(expr.Exps)-1)
	for i, argExpr := range expr.Exps[1:] {
		argObj, err := evalExpressionWithTailCall(argExpr, env, false) // Arguments are not in tail position
		if err != nil {
			return nil, err
		}
		args[i] = argObj
	}

	// If we're in a tail position, return a TailCallResult instead of directly applying
	if inTailPosition {
		return &TailCallResult{
			Proc: f,
			Args: args,
			Env:  env,
		}, nil
	}

	// Otherwise, apply the procedure normally
	return applyObjectProcedure(f, args, env)
}

// evalApplicationWithTailCall evaluates an application with tail call optimization
func evalApplicationWithTailCall(expr *ast.ListExp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	// Evaluate the first element to get the procedure
	procObj, err := evalExpressionWithTailCall(expr.Exps[0], env, false) // Procedure expression is not in tail position
	if err != nil {
		return nil, err
	}

	// Evaluate the arguments
	args := make([]obj.Object, len(expr.Exps)-1)
	for i, argExpr := range expr.Exps[1:] {
		argObj, err := evalExpressionWithTailCall(argExpr, env, false) // Arguments are not in tail position
		if err != nil {
			return nil, err
		}
		args[i] = argObj
	}

	// If we're in a tail position, return a TailCallResult instead of directly applying
	if inTailPosition {
		return &TailCallResult{
			Proc: procObj,
			Args: args,
			Env:  env,
		}, nil
	}

	// Apply the procedure
	return applyObjectProcedure(procObj, args, env)
}

func evalApplication(expr *ast.ListExp, env *obj.Environment) (obj.Object, error) {
	// Evaluate the first element to get the procedure
	procObj, err := EvalExpression(expr.Exps[0], env)
	if err != nil {
		return nil, err
	}

	// Evaluate the arguments
	args := make([]obj.Object, len(expr.Exps)-1)
	for i, argExpr := range expr.Exps[1:] {
		argObj, err := EvalExpression(argExpr, env)
		if err != nil {
			return nil, err
		}
		args[i] = argObj
	}

	// Apply the procedure
	return applyObjectProcedure(procObj, args, env)
}

func evalProcedure(expr *ast.ListExp, env *obj.Environment) (obj.Object, error) {
	fname := expr.Exps[0].(*ast.IdentifierExp).Token.Lexeme
	f, ok := env.Get(fname)
	if !ok {
		return nil, fmt.Errorf("undefined: %s", fname)
	}

	// Evaluate arguments
	args := make([]obj.Object, len(expr.Exps)-1)
	for i, argExpr := range expr.Exps[1:] {
		argObj, err := EvalExpression(argExpr, env)
		if err != nil {
			return nil, err
		}
		args[i] = argObj
	}

	return applyObjectProcedure(f, args, env)
}

func applyProcedure(f obj.Object, args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	objs := make([]obj.Object, len(args))
	for i, arg := range args {
		if obj, err := EvalExpression(arg, env); err != nil {
			return nil, err
		} else {
			objs[i] = obj
		}
	}

	switch proc := f.(type) {
	case *obj.Builtin:
		result := proc.Fn(objs, env)
		if result.Type() == obj.ERROR_OBJ {
			return nil, errors.New(result.Inspect())
		}
		return result, nil
	case *obj.Procedure:
		// Create a new environment for the procedure call
		newEnv := obj.NewEnclosedEnvironment(proc.Env)

		// Bind parameters to arguments
		if len(proc.Params) != len(objs) {
			return nil, fmt.Errorf("wrong number of arguments to %s: expected %d, got %d",
				proc.Name, len(proc.Params), len(objs))
		}

		for i, param := range proc.Params {
			newEnv.Set(param, objs[i])
		}

		// Evaluate the procedure body
		var result obj.Object
		var err error
		for _, expr := range proc.Body {
			result, err = EvalExpression(expr, newEnv)
			if err != nil {
				return nil, err
			}
		}

		return result, nil
	default:
		return nil, fmt.Errorf("attempt to apply non-procedure")
	}
}

func applyDefine(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// Check if we have at least 2 arguments
	if len(args) < 2 {
		return nil, errors.New("define requires at least 2 arguments")
	}

	// Handle(define name value) form - first argument is a symbol
	if nameExpr, ok := args[0].(*ast.IdentifierExp); ok {
		// Evaluate the value expression
		if value, err := EvalExpression(args[1], env); err != nil {
			return nil, err
		} else {
			// In the environment, set the variable
			env.Set(nameExpr.Token.Lexeme, value)
			return obj.NULL_OBJECT, nil
		}
	}

	// Handle(define (name arg1 arg2 ...) body) form - first argument is a list
	if procDefExpr, ok := args[0].(*ast.ListExp); ok {
		if len(procDefExpr.Exps) < 1 {
			return nil, errors.New("invalid procedure definition in define")
		}

		// Check if the procedure name is a symbol
		procNameExpr, ok := procDefExpr.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, fmt.Errorf("procedure name %v must be an Identifier", procDefExpr.Exps[0])
		}

		// Extract parameters
		var params []string
		for i := 1; i < len(procDefExpr.Exps); i++ {
			paramExpr, ok := procDefExpr.Exps[i].(*ast.IdentifierExp)
			if !ok {
				return nil, fmt.Errorf("procedure name %v must be an Identifier", procDefExpr.Exps[0])
			}
			params = append(params, paramExpr.Token.Lexeme)
		}

		// Create a procedure object
		proc := &obj.Procedure{
			Name:   procNameExpr.Token.Lexeme,
			Params: params,
			Body:   args[1:], // The body consists of all expressions after the procedure definition
			Env:    env,
		}

		// In the environment, set the procedure
		env.Set(procNameExpr.Token.Lexeme, proc)
		return obj.NULL_OBJECT, nil
	}

	return nil, errors.New("define expects an identifer or a procedure definition as the first argument")
}

func applyLet(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// Check if we have at least the bindings and one body expression
	if len(args) < 2 {
		return nil, errors.New("let requires at least bindings and one body expression")
	}

	// First argument should be the bindings list
	bindings, ok := args[0].(*ast.ListExp)
	if !ok {
		return nil, errors.New("let bindings must be a list")
	}

	// Create a new enclosed environment
	newEnv := obj.NewEnclosedEnvironment(env)

	// Process each binding
	for _, binding := range bindings.Exps {
		// Each binding should be a list of (var value)
		bindingList, ok := binding.(*ast.ListExp)
		if !ok {
			return nil, errors.New("let binding must be a list")
		}

		// Check if binding has exactly two elements
		if len(bindingList.Exps) != 2 {
			return nil, errors.New("let binding must have exactly two elements: variable and value")
		}

		// First element should be a symbol (variable name)
		varNode, ok := bindingList.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, errors.New("let variable must be an identifier")
		}

		// Second element is the value expression
		valExpr := bindingList.Exps[1]

		// Evaluate the value expression in the current environment (outer scope)
		val, err := EvalExpression(valExpr, env)
		if err != nil {
			return nil, err
		}

		// Bind the variable to the value in the new environment
		newEnv.Set(varNode.Token.Lexeme, val)
	}

	/*
		// Evaluate body expressions in the new environment
		var result Object
		var err error
		for i := 1; i < len(args); i++ {
			result, err = Eval(args[i], newEnv)
			if err != nil {
				return nil, err
			}
		}
	*/
	return EvalProgram(args[1:], newEnv)
}

func applyLetRec(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// Check if we have at least the bindings and one body expression
	if len(args) < 2 {
		return nil, errors.New("letrec requires at least bindings and one body expression")
	}

	// First argument should be the bindings list
	bindings, ok := args[0].(*ast.ListExp)
	if !ok {
		return nil, errors.New("letrec bindings must be a list")
	}

	// Create a new enclosed environment
	newEnv := obj.NewEnclosedEnvironment(env)

	// First pass: Create placeholder bindings in the new environment
	// This allows recursive references during the evaluation of the binding values
	for _, binding := range bindings.Exps {
		// Each binding should be a list of (var value)
		bindingList, ok := binding.(*ast.ListExp)
		if !ok {
			return nil, errors.New("letrec binding must be a list")
		}

		// Check if binding has exactly two elements
		if len(bindingList.Exps) != 2 {
			return nil, errors.New("letrec binding must have exactly two elements: variable and value")
		}

		// First element should be a symbol (variable name)
		varNode, ok := bindingList.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, errors.New("letrec variable must be an identifier")
		}

		// Create a placeholder value in the new environment
		// This allows variables to reference each other during evaluation
		newEnv.Set(varNode.Token.Lexeme, &obj.NullObject{})
	}

	// Second pass: Evaluate each binding value in the new environment and update bindings
	for _, binding := range bindings.Exps {
		bindingList := binding.(*ast.ListExp)

		// Get the variable name
		varNode := bindingList.Exps[0].(*ast.IdentifierExp)

		// Second element is the value expression
		valExpr := bindingList.Exps[1]

		// Evaluate the value expression in the new environment (which now contains all variable names)
		val, err := EvalExpression(valExpr, newEnv)
		if err != nil {
			return nil, err
		}

		// Update the variable binding with the evaluated value
		newEnv.Set(varNode.Token.Lexeme, val)
	}

	return EvalProgram(args[1:], newEnv)
}

// evalCondTest evaluates a test expression in a cond clause
func evalCondTest(test ast.Exp, env *obj.Environment) obj.Object {
	result, err := EvalExpression(test, env)
	if err != nil {
		// In a real implementation, we might want to handle this error differently
		return obj.NULL_OBJECT
	}
	return result
}

func EvalProgram(program []ast.Exp, env *obj.Environment) (obj.Object, error) {
	var result obj.Object
	var err error
	for _, expr := range program {
		result, err = EvalExpression(expr, env)
		if err != nil {
			return nil, err
		}
	}
	return result, nil
}

// applyIfWithTailCall evaluates an if expression with tail call optimization
func applyIfWithTailCall(args []ast.Exp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	// Check if we have at least 2 arguments (test and consequent)
	// The alternative argument is optional
	if len(args) < 2 {
		return nil, errors.New("if requires at least 2 arguments")
	}

	// Evaluate the test condition
	test, err := evalExpressionWithTailCall(args[0], env, false) // Test is not in tail position
	if err != nil {
		return nil, err
	}

	// Check if test is true
	truthy := isTruthy(test)

	if truthy {
		// Evaluate the consequent in tail position if we're in a tail position
		return evalExpressionWithTailCall(args[1], env, inTailPosition)
	} else if len(args) >= 3 {
		// Evaluate the alternative in tail position if we're in a tail position
		return evalExpressionWithTailCall(args[2], env, inTailPosition)
	} else {
		// Return null if no alternative and test is false
		return obj.NULL_OBJECT, nil
	}
}

// applyAndWithTailCall evaluates an and expression with tail call optimization
func applyAndWithTailCall(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// If no expressions are provided, return #t
	if len(args) == 0 {
		return &obj.BooleanObject{Value: true}, nil
	}

	// Evaluate expressions from left to right
	for _, expr := range args {
		result, err := evalExpressionWithTailCall(expr, env, false)
		if err != nil {
			return nil, err
		}

		// If any expression evaluates to #f, return #f immediately
		if !isTruthy(result) {
			return &obj.BooleanObject{Value: false}, nil
		}
	}

	// If all expressions are true, return the value of the last expression
	return evalExpressionWithTailCall(args[len(args)-1], env, false)
}

// applyOrWithTailCall evaluates an or expression with tail call optimization
func applyOrWithTailCall(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// If no expressions are provided, return #f
	if len(args) == 0 {
		return &obj.BooleanObject{Value: false}, nil
	}

	// Evaluate expressions from left to right
	for _, expr := range args {
		// Only the last expression could potentially be in a tail position,
		// but since we're in an or expression, it's not truly a tail position
		result, err := evalExpressionWithTailCall(expr, env, false)
		if err != nil {
			return nil, err
		}

		// If any expression evaluates to a true value, return that value immediately
		if isTruthy(result) {
			return result, nil
		}
	}

	// If all expressions are false, return #f
	return &obj.BooleanObject{Value: false}, nil
}

// applyBeginWithTailCall evaluates a begin expression with tail call optimization
func applyBeginWithTailCall(args []ast.Exp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	// begin with no arguments returns null
	if len(args) == 0 {
		return obj.NULL_OBJECT, nil
	}

	// Evaluate each expression in sequence
	for i := 0; i < len(args)-1; i++ {
		_, err := evalExpressionWithTailCall(args[i], env, false)
		if err != nil {
			return nil, err
		}
	}

	// The last expression is in tail position if we're in a tail position
	return evalExpressionWithTailCall(args[len(args)-1], env, inTailPosition)
}

// applyCondWithTailCall evaluates a cond expression with tail call optimization
func applyCondWithTailCall(args []ast.Exp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	// A cond expression must have at least one clause
	if len(args) < 1 {
		return nil, errors.New("cond requires at least one clause")
	}

	// Process each clause
	for _, arg := range args {
		// Each clause must be a list
		clause, ok := arg.(*ast.ListExp)
		if !ok {
			return nil, errors.New("cond clauses must be lists")
		}

		// Each clause must have at least one element (the test)
		if len(clause.Exps) < 1 {
			return nil, errors.New("cond clauses must have at least one element")
		}

		// Check for else clause (special case)
		elseClause := false
		if ident, ok := clause.Exps[0].(*ast.IdentifierExp); ok {
			if ident.Token.Lexeme == "else" {
				elseClause = true
			}
		}

		// If this is an else clause, execute its body in tail position if we're in a tail position
		if elseClause {
			// Need at least one expression in the body
			if len(clause.Exps) < 2 {
				return obj.NULL_OBJECT, nil
			}

			// All but the last expression are not in tail position
			for i := 1; i < len(clause.Exps)-1; i++ {
				_, err := evalExpressionWithTailCall(clause.Exps[i], env, false)
				if err != nil {
					return nil, err
				}
			}

			// The last expression is in tail position if we're in a tail position
			return evalExpressionWithTailCall(clause.Exps[len(clause.Exps)-1], env, inTailPosition)
		}

		// Evaluate the test condition
		testResult, err := evalExpressionWithTailCall(clause.Exps[0], env, false) // Test is not in tail position
		if err != nil {
			return nil, err
		}

		// If the test is true (truthy), execute the body
		if isTruthy(testResult) {
			// If there are no expressions in the body, return the test result
			if len(clause.Exps) < 2 {
				return testResult, nil
			}

			// All but the last expression are not in tail position
			for i := 1; i < len(clause.Exps)-1; i++ {
				_, err := evalExpressionWithTailCall(clause.Exps[i], env, false)
				if err != nil {
					return nil, err
				}
			}

			// The last expression is in tail position if we're in a tail position
			return evalExpressionWithTailCall(clause.Exps[len(clause.Exps)-1], env, inTailPosition)
		}
	}

	// If no clause matched, return null
	return obj.NULL_OBJECT, nil
}

// applyCaseWithTailCall evaluates a case expression with tail call optimization
func applyCaseWithTailCall(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// Case requires at least a key and one clause
	if len(args) < 2 {
		return nil, errors.New("case requires at least a key and one clause")
	}

	// Evaluate the key expression
	keyValue, err := evalExpressionWithTailCall(args[0], env, false) // Key is not in tail position
	if err != nil {
		return nil, err
	}

	// Process each clause
	for i := 1; i < len(args); i++ {
		clause := args[i]

		// Each clause must be a list
		clauseList, ok := clause.(*ast.ListExp)
		if !ok {
			return nil, errors.New("case clauses must be lists")
		}

		// Each clause must have at least one element
		if len(clauseList.Exps) < 1 {
			return nil, errors.New("case clauses must have at least one element")
		}

		// Check for else clause (special case)
		elseClause := false
		if ident, ok := clauseList.Exps[0].(*ast.IdentifierExp); ok {
			if ident.Token.Lexeme == "else" {
				elseClause = true
			}
		}

		// If this is an else clause, execute its body
		// All but the last expression are not in tail position
		if elseClause {
			// Need at least one expression in the body
			if len(clauseList.Exps) < 2 {
				return obj.NULL_OBJECT, nil
			}

			// All but the last expression are not in tail position
			for j := 1; j < len(clauseList.Exps)-1; j++ {
				_, err := evalExpressionWithTailCall(clauseList.Exps[j], env, false)
				if err != nil {
					return nil, err
				}
			}

			// The last expression is in tail position
			return evalExpressionWithTailCall(clauseList.Exps[len(clauseList.Exps)-1], env, true)
		}

		// The first element should be a list of datums
		datumList, ok := clauseList.Exps[0].(*ast.ListExp)
		if !ok {
			return nil, errors.New("first element of case clause must be a list of datums or else")
		}

		// Check if any datum in this clause matches the key
		match := false
		for _, datum := range datumList.Exps {
			// Evaluate the datum
			datumValue, err := evalExpressionWithTailCall(datum, env, false) // Datums are not in tail position
			if err != nil {
				return nil, err
			}

			// Compare the key with the datum using eqv?
			if isEqual(keyValue, datumValue) {
				match = true
				break
			}
		}

		// If there's a match, evaluate the body
		if match {
			// If there are no expressions in the body, return null
			if len(clauseList.Exps) < 2 {
				return obj.NULL_OBJECT, nil
			}

			// All but the last expression are not in tail position
			for j := 1; j < len(clauseList.Exps)-1; j++ {
				_, err := evalExpressionWithTailCall(clauseList.Exps[j], env, false)
				if err != nil {
					return nil, err
				}
			}

			// The last expression is in tail position
			return evalExpressionWithTailCall(clauseList.Exps[len(clauseList.Exps)-1], env, true)
		}
	}

	// If no clause matched, return unspecified value (we'll use null)
	return obj.NULL_OBJECT, nil
}

// applyLetWithTailCall evaluates a let expression with tail call optimization
func applyLetWithTailCall(args []ast.Exp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	// Check if we have at least the bindings and one body expression
	if len(args) < 2 {
		return nil, errors.New("let requires at least bindings and one body expression")
	}

	// First argument should be the bindings list
	bindings, ok := args[0].(*ast.ListExp)
	if !ok {
		return nil, errors.New("let bindings must be a list")
	}

	// Create a new enclosed environment
	newEnv := obj.NewEnclosedEnvironment(env)

	// Process each binding
	for _, binding := range bindings.Exps {
		// Each binding should be a list of (var value)
		bindingList, ok := binding.(*ast.ListExp)
		if !ok {
			return nil, errors.New("let binding must be a list")
		}

		// Check if binding has exactly two elements
		if len(bindingList.Exps) != 2 {
			return nil, errors.New("let binding must have exactly two elements: variable and value")
		}

		// First element should be a symbol (variable name)
		varNode, ok := bindingList.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, errors.New("let variable must be an identifier")
		}

		// Second element is the value expression
		valExpr := bindingList.Exps[1]

		// Evaluate the value expression in the current environment (outer scope)
		val, err := evalExpressionWithTailCall(valExpr, env, false) // Bindings are not in tail position
		if err != nil {
			return nil, err
		}

		// Bind the variable to the value in the new environment
		newEnv.Set(varNode.Token.Lexeme, val)
	}

	// Evaluate body expressions in the new environment
	// All but the last expression are not in tail position
	for i := 1; i < len(args)-1; i++ {
		_, err := evalExpressionWithTailCall(args[i], newEnv, false)
		if err != nil {
			return nil, err
		}
	}

	// The last expression is in tail position if we're in a tail position
	if len(args) > 1 {
		return evalExpressionWithTailCall(args[len(args)-1], newEnv, inTailPosition)
	}

	return obj.NULL_OBJECT, nil
}

// applyLetRecWithTailCall evaluates a letrec expression with tail call optimization
func applyLetRecWithTailCall(args []ast.Exp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	// Check if we have at least the bindings and one body expression
	if len(args) < 2 {
		return nil, errors.New("letrec requires at least bindings and one body expression")
	}

	// First argument should be the bindings list
	bindings, ok := args[0].(*ast.ListExp)
	if !ok {
		return nil, errors.New("letrec bindings must be a list")
	}

	// Create a new enclosed environment
	newEnv := obj.NewEnclosedEnvironment(env)

	// First pass: Create placeholder bindings in the new environment
	// This allows recursive references during the evaluation of the binding values
	for _, binding := range bindings.Exps {
		// Each binding should be a list of (var value)
		bindingList, ok := binding.(*ast.ListExp)
		if !ok {
			return nil, errors.New("letrec binding must be a list")
		}

		// Check if binding has exactly two elements
		if len(bindingList.Exps) != 2 {
			return nil, errors.New("letrec binding must have exactly two elements: variable and value")
		}

		// First element should be a symbol (variable name)
		varNode, ok := bindingList.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, errors.New("letrec variable must be an identifier")
		}

		// Create a placeholder value in the new environment
		// This allows variables to reference each other during evaluation
		newEnv.Set(varNode.Token.Lexeme, &obj.NullObject{})
	}

	// Second pass: Evaluate each binding value in the new environment and update bindings
	for _, binding := range bindings.Exps {
		bindingList := binding.(*ast.ListExp)

		// Get the variable name
		varNode := bindingList.Exps[0].(*ast.IdentifierExp)

		// Second element is the value expression
		valExpr := bindingList.Exps[1]

		// Evaluate the value expression in the new environment (which now contains all variable names)
		val, err := evalExpressionWithTailCall(valExpr, newEnv, false) // Bindings are not in tail position
		if err != nil {
			return nil, err
		}

		// Update the variable binding with the evaluated value
		newEnv.Set(varNode.Token.Lexeme, val)
	}

	// Evaluate body expressions in the new environment
	// All but the last expression are not in tail position
	for i := 1; i < len(args)-1; i++ {
		_, err := evalExpressionWithTailCall(args[i], newEnv, false)
		if err != nil {
			return nil, err
		}
	}

	// The last expression is in tail position if we're in a tail position
	if len(args) > 1 {
		return evalExpressionWithTailCall(args[len(args)-1], newEnv, inTailPosition)
	}

	return obj.NULL_OBJECT, nil
}

// applyLetStarWithTailCall evaluates a let* expression with tail call optimization
func applyLetStarWithTailCall(args []ast.Exp, env *obj.Environment, inTailPosition bool) (obj.Object, error) {
	// Check if we have at least the bindings and one body expression
	if len(args) < 2 {
		return nil, errors.New("let* requires at least bindings and one body expression")
	}

	// First argument should be the bindings list
	bindings, ok := args[0].(*ast.ListExp)
	if !ok {
		return nil, errors.New("let* bindings must be a list")
	}

	// Create a new enclosed environment
	newEnv := obj.NewEnclosedEnvironment(env)

	// Process each binding sequentially, making each binding visible to subsequent ones
	for _, binding := range bindings.Exps {
		// Each binding should be a list of (var value)
		bindingList, ok := binding.(*ast.ListExp)
		if !ok {
			return nil, errors.New("let* binding must be a list")
		}

		// Check if binding has exactly two elements
		if len(bindingList.Exps) != 2 {
			return nil, errors.New("let* binding must have exactly two elements: variable and value")
		}

		// First element should be a symbol (variable name)
		varNode, ok := bindingList.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, errors.New("let* variable must be an identifier")
		}

		// Second element is the value expression
		valExpr := bindingList.Exps[1]

		// Evaluate the value expression in the new environment (where previous bindings are visible)
		val, err := evalExpressionWithTailCall(valExpr, newEnv, false) // Bindings are not in tail position
		if err != nil {
			return nil, err
		}

		// Bind the variable to the value in the new environment
		newEnv.Set(varNode.Token.Lexeme, val)
	}

	// Evaluate body expressions in the new environment
	// All but the last expression are not in tail position
	for i := 1; i < len(args)-1; i++ {
		_, err := evalExpressionWithTailCall(args[i], newEnv, false)
		if err != nil {
			return nil, err
		}
	}

	// The last expression is in tail position if we're in a tail position
	if len(args) > 1 {
		return evalExpressionWithTailCall(args[len(args)-1], newEnv, inTailPosition)
	}

	return obj.NULL_OBJECT, nil
}

func applyCond(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// A cond expression must have at least one clause
	if len(args) < 1 {
		return nil, errors.New("cond requires at least one clause")
	}

	// Process each clause
	for _, arg := range args {
		// Each clause must be a list
		clause, ok := arg.(*ast.ListExp)
		if !ok {
			return nil, errors.New("cond clauses must be lists")
		}

		// Each clause must have at least one element (the test)
		if len(clause.Exps) < 1 {
			return nil, errors.New("cond clauses must have at least one element")
		}

		// Check for else clause (special case)
		elseClause := false
		if ident, ok := clause.Exps[0].(*ast.IdentifierExp); ok {
			if ident.Token.Lexeme == "else" {
				elseClause = true
			}
		}

		// If this is an else clause, execute its body
		if elseClause {
			// Evaluate all expressions in the body and return the value of the last one
			var result obj.Object = obj.NULL_OBJECT
			var err error

			// Need at least one expression in the body
			if len(clause.Exps) < 2 {
				return obj.NULL_OBJECT, nil
			}

			for i := 1; i < len(clause.Exps); i++ {
				result, err = EvalExpression(clause.Exps[i], env)
				if err != nil {
					return nil, err
				}
			}

			return result, nil
		}

		// Evaluate the test condition
		testResult, err := EvalExpression(clause.Exps[0], env)
		if err != nil {
			return nil, err
		}

		// If the test is true (truthy), execute the body
		if isTruthy(testResult) {
			// Evaluate all expressions in the body and return the value of the last one
			var result obj.Object = obj.NULL_OBJECT
			var err error

			// If there are no expressions in the body, return the test result
			if len(clause.Exps) < 2 {
				return testResult, nil
			}

			for i := 1; i < len(clause.Exps); i++ {
				result, err = EvalExpression(clause.Exps[i], env)
				if err != nil {
					return nil, err
				}
			}

			return result, nil
		}
	}

	// If no clause matched, return null
	return obj.NULL_OBJECT, nil
}

func applyBegin(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// begin requires at least one argument
	//if len(args) == 0 {
	//	return nil, errors.New("begin requires at least one argument")
	//}

	// Evaluate each expression in sequence
	var result obj.Object = obj.NULL_OBJECT
	var err error

	for _, expr := range args {
		result, err = EvalExpression(expr, env)
		if err != nil {
			return nil, err
		}
	}

	// Return the value of the last expression
	return result, nil
}

func applySetBang(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// set! requires exactly 2 arguments: variable and expression
	if len(args) != 2 {
		return nil, errors.New("set! requires exactly 2 arguments")
	}

	// First argument must be an identifier (variable name)
	varExpr, ok := args[0].(*ast.IdentifierExp)
	if !ok {
		return nil, errors.New("first argument to set! must be an identifier")
	}

	// Check if the variable exists in any environment
	_, exists := env.Get(varExpr.Token.Lexeme)
	if !exists {
		return nil, fmt.Errorf("variable %s is not defined", varExpr.Token.Lexeme)
	}

	// Evaluate the expression to get the new value
	newValue, err := EvalExpression(args[1], env)
	if err != nil {
		return nil, err
	}

	// Update the variable in the environment where it was originally defined
	env.Update(varExpr.Token.Lexeme, newValue)

	// set! returns an unspecified value, we'll use NULL_OBJECT
	return obj.NULL_OBJECT, nil
}

func applyCase(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// Case requires at least a key and one clause
	if len(args) < 2 {
		return nil, errors.New("case requires at least a key and one clause")
	}

	// Evaluate the key expression
	keyValue, err := EvalExpression(args[0], env)
	if err != nil {
		return nil, err
	}

	// Process each clause
	for i := 1; i < len(args); i++ {
		clause := args[i]

		// Each clause must be a list
		clauseList, ok := clause.(*ast.ListExp)
		if !ok {
			return nil, errors.New("case clauses must be lists")
		}

		// Each clause must have at least one element
		if len(clauseList.Exps) < 1 {
			return nil, errors.New("case clauses must have at least one element")
		}

		// Check for else clause (special case)
		elseClause := false
		if ident, ok := clauseList.Exps[0].(*ast.IdentifierExp); ok {
			if ident.Token.Lexeme == "else" {
				elseClause = true
			}
		}

		// If this is an else clause, execute its body
		if elseClause {
			// Evaluate all expressions in the body and return the value of the last one
			var result obj.Object = obj.NULL_OBJECT
			var err error

			// Need at least one expression in the body
			if len(clauseList.Exps) < 2 {
				return obj.NULL_OBJECT, nil
			}

			for j := 1; j < len(clauseList.Exps); j++ {
				result, err = EvalExpression(clauseList.Exps[j], env)
				if err != nil {
					return nil, err
				}
			}

			return result, nil
		}

		// The first element should be a list of datums
		datumList, ok := clauseList.Exps[0].(*ast.ListExp)
		if !ok {
			return nil, errors.New("first element of case clause must be a list of datums or else")
		}

		// Check if any datum in this clause matches the key
		match := false
		for _, datum := range datumList.Exps {
			// Evaluate the datum
			datumValue, err := EvalExpression(datum, env)
			if err != nil {
				return nil, err
			}

			// Compare the key with the datum using eqv?
			if isEqual(keyValue, datumValue) {
				match = true
				break
			}
		}

		// If there's a match, evaluate the body
		if match {
			// Evaluate all expressions in the body and return the value of the last one
			var result obj.Object = obj.NULL_OBJECT
			var err error

			// If there are no expressions in the body, return null
			if len(clauseList.Exps) < 2 {
				return obj.NULL_OBJECT, nil
			}

			for j := 1; j < len(clauseList.Exps); j++ {
				result, err = EvalExpression(clauseList.Exps[j], env)
				if err != nil {
					return nil, err
				}
			}

			return result, nil
		}
	}

	// If no clause matched, return unspecified value (we'll use null)
	return obj.NULL_OBJECT, nil
}

// isEqual checks if two objects are equal using Scheme's eqv? semantics
func isEqual(a, b obj.Object) bool {
	// Use the existing Eqv function to compare objects
	env := obj.NewEnvironment()
	result := Eqv([]obj.Object{a, b}, env)

	// Check if the result is a boolean object with value true
	if boolResult, ok := result.(*obj.BooleanObject); ok {
		return boolResult.Value
	}

	// If not a boolean object, return false
	return false
}

func applyAnd(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// If no expressions are provided, return #t
	if len(args) == 0 {
		return &obj.BooleanObject{Value: true}, nil
	}

	// Evaluate expressions from left to right
	var result obj.Object
	for _, expr := range args {
		var err error
		result, err = EvalExpression(expr, env)
		if err != nil {
			return nil, err
		}

		// If any expression evaluates to #f, return #f immediately
		if !isTruthy(result) {
			return &obj.BooleanObject{Value: false}, nil
		}
	}

	// If all expressions are true, return the value of the last expression
	return result, nil
}

func applyOr(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// If no expressions are provided, return #f
	if len(args) == 0 {
		return &obj.BooleanObject{Value: false}, nil
	}

	// Evaluate expressions from left to right
	for _, expr := range args {
		result, err := EvalExpression(expr, env)
		if err != nil {
			return nil, err
		}

		// If any expression evaluates to a true value, return that value immediately
		if isTruthy(result) {
			return result, nil
		}
	}

	// If all expressions are false, return #f
	return &obj.BooleanObject{Value: false}, nil
}

func applyLetStar(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// Check if we have at least the bindings and one body expression
	if len(args) < 2 {
		return nil, errors.New("let* requires at least bindings and one body expression")
	}

	// First argument should be the bindings list
	bindings, ok := args[0].(*ast.ListExp)
	if !ok {
		return nil, errors.New("let* bindings must be a list")
	}

	// Create a new enclosed environment
	newEnv := obj.NewEnclosedEnvironment(env)

	// Process each binding sequentially, making each binding visible to subsequent ones
	for _, binding := range bindings.Exps {
		// Each binding should be a list of (var value)
		bindingList, ok := binding.(*ast.ListExp)
		if !ok {
			return nil, errors.New("let* binding must be a list")
		}

		// Check if binding has exactly two elements
		if len(bindingList.Exps) != 2 {
			return nil, errors.New("let* binding must have exactly two elements: variable and value")
		}

		// First element should be a symbol (variable name)
		varNode, ok := bindingList.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, errors.New("let* variable must be an identifier")
		}

		// Second element is the value expression
		valExpr := bindingList.Exps[1]

		// Evaluate the value expression in the new environment (where previous bindings are visible)
		val, err := EvalExpression(valExpr, newEnv)
		if err != nil {
			return nil, err
		}

		// Bind the variable to the value in the new environment
		newEnv.Set(varNode.Token.Lexeme, val)
	}

	return EvalProgram(args[1:], newEnv)
}

func applyDo(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	// Check if we have at least the bindings, test clause, and optionally body expressions
	if len(args) < 2 {
		return nil, errors.New("do requires at least bindings and test clause")
	}

	// First argument should be the bindings list
	bindings, ok := args[0].(*ast.ListExp)
	if !ok {
		return nil, errors.New("do bindings must be a list")
	}

	// Second argument should be the test clause
	testClause, ok := args[1].(*ast.ListExp)
	if !ok {
		return nil, errors.New("do test clause must be a list")
	}

	if len(testClause.Exps) < 1 {
		return nil, errors.New("do test clause must have at least a test expression")
	}

	// Create a new enclosed environment for the do loop
	loopEnv := obj.NewEnclosedEnvironment(env)

	// Extract variable names, initial values, and step expressions
	var variables []string
	var initValues []ast.Exp
	var stepExps []ast.Exp

	for _, binding := range bindings.Exps {
		bindingList, ok := binding.(*ast.ListExp)
		if !ok {
			return nil, errors.New("do binding must be a list")
		}

		// Check if binding has 2 or 3 elements (variable init [step])
		if len(bindingList.Exps) < 2 || len(bindingList.Exps) > 3 {
			return nil, errors.New("do binding must have 2 or 3 elements: variable, init, and optional step")
		}

		// First element should be a symbol (variable name)
		varNode, ok := bindingList.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, errors.New("do variable must be an identifier")
		}

		variables = append(variables, varNode.Token.Lexeme)
		initValues = append(initValues, bindingList.Exps[1])

		// If step expression is provided, use it, otherwise use variable name as step
		if len(bindingList.Exps) == 3 {
			stepExps = append(stepExps, bindingList.Exps[2])
		} else {
			stepExps = append(stepExps, varNode)
		}
	}

	// Initialize variables with their initial values
	for i, initExpr := range initValues {
		initVal, err := EvalExpression(initExpr, env)
		if err != nil {
			return nil, err
		}
		loopEnv.Set(variables[i], initVal)
	}

	// Main loop
	for {
		// Evaluate test expression
		testResult, err := EvalExpression(testClause.Exps[0], loopEnv)
		if err != nil {
			return nil, err
		}

		// If test is true, evaluate and return the result expressions (if any)
		if isTruthy(testResult) {
			if len(testClause.Exps) == 1 {
				// No result expressions, return unspecified value (null)
				return obj.NULL_OBJECT, nil
			}

			// Evaluate result expressions and return the value of the last one
			var result obj.Object
			for i := 1; i < len(testClause.Exps); i++ {
				result, err = EvalExpression(testClause.Exps[i], loopEnv)
				if err != nil {
					return nil, err
				}
			}
			return result, nil
		}

		// Execute body commands (if any)
		for i := 2; i < len(args); i++ {
			_, err := EvalExpression(args[i], loopEnv)
			if err != nil {
				return nil, err
			}
		}

		// Evaluate step expressions and update variables
		newValues := make([]obj.Object, len(variables))
		for i, stepExpr := range stepExps {
			newValue, err := EvalExpression(stepExpr, loopEnv)
			if err != nil {
				return nil, err
			}
			newValues[i] = newValue
		}

		// Update all variables in the loop environment
		for i, variable := range variables {
			loopEnv.Set(variable, newValues[i])
		}
	}
}

func applyDelay(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("delay requires exactly 1 argument")
	}

	// Create a promise object
	promise := &obj.PromiseObject{
		Expression: args[0],
		Env:        env, // Use the original environment
		Forced:     false,
		Result:     nil,
	}

	return promise, nil
}

// applyForce evaluates a promise
func applyForce(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("force requires exactly 1 argument")
	}

	// Evaluate the argument to get the promise
	promiseObj, err := EvalExpression(args[0], env)
	if err != nil {
		return nil, err
	}

	// Check if it's a promise
	promise, ok := promiseObj.(*obj.PromiseObject)
	if !ok {
		// According to R4RS, if the argument is not a promise, return it as is
		return promiseObj, nil
	}

	// If already forced, return cached result
	if promise.Forced {
		return promise.Result, nil
	}

	// Evaluate the promise and cache the result
	result, err := EvalExpression(promise.Expression.(ast.Exp), promise.Env)
	if err != nil {
		return nil, err
	}

	promise.Forced = true
	promise.Result = result

	return result, nil
}

func evalQuasiquote(exp *ast.QuasiquoteExp, env *obj.Environment) (obj.Object, error) {
	return quasiquoteExpressionToObject(exp.Value, env)
}

func evalUnquote(exp *ast.UnquoteExp, env *obj.Environment) (obj.Object, error) {
	// Unquote should only appear within a quasiquote context
	// For now, we'll evaluate the expression directly
	return EvalExpression(exp.Value, env)
}

func evalUnquoteSplicing(exp *ast.UnquoteSplicingExp, env *obj.Environment) (obj.Object, error) {
	// Unquote-splicing should only appear within a quasiquote context
	// For now, we'll evaluate the expression directly
	return EvalExpression(exp.Value, env)
}

func quasiquoteExpressionToObject(exp ast.Exp, env *obj.Environment) (obj.Object, error) {
	return quasiquoteExpressionToObjectWithLevel(exp, env, 0)
}

func quasiquoteExpressionToObjectWithLevel(exp ast.Exp, env *obj.Environment, level int) (obj.Object, error) {
	switch e := exp.(type) {
	case *ast.BooleanExp:
		return &obj.BooleanObject{Value: e.Token.Literal.(bool)}, nil
	case *ast.IdentifierExp:
		return &obj.SymbolObject{Value: e.Token.Lexeme}, nil
	case *ast.NumberExp:
		return &obj.NumberObject{Value: e.Token.Literal.(num.Number)}, nil
	case *ast.StringExp:
		return &obj.StringObject{Value: e.Token.Literal.(string)}, nil
	case *ast.CharacterExp:
		return &obj.CharacterObject{Value: rune(e.Token.Literal.(byte))}, nil
	case *ast.QuoteExp:
		// Handle quote expressions within quasiquote
		// This should create a list with 'quote as the first element
		// and the quoted value as the second element
		symbol := &obj.SymbolObject{Value: "quote"}
		quotedValue, err := quasiquoteExpressionToObjectWithLevel(e.Value, env, level)
		if err != nil {
			return nil, err
		}
		return &obj.PairObject{
			Car: symbol,
			Cdr: &obj.PairObject{
				Car: quotedValue,
				Cdr: obj.EMPTY_LIST,
			},
		}, nil
	case *ast.UnquoteExp:
		// Only unquote if we're at the right nesting level
		if level == 0 {
			// This is where the real unquoting happens
			return EvalExpression(e.Value, env)
		} else {
			// Still nested - treat as regular list with unquote
			symbol := &obj.SymbolObject{Value: "unquote"}
			value, err := quasiquoteExpressionToObjectWithLevel(e.Value, env, level-1)
			if err != nil {
				return nil, err
			}
			return &obj.PairObject{
				Car: symbol,
				Cdr: &obj.PairObject{
					Car: value,
					Cdr: obj.EMPTY_LIST,
				},
			}, nil
		}
	case *ast.UnquoteSplicingExp:
		// Only unquote-splicing if we're at the right nesting level
		if level == 0 {
			// This is where the real unquote-splicing happens
			return EvalExpression(e.Value, env)
		} else {
			// Still nested - treat as regular list with unquote-splicing
			symbol := &obj.SymbolObject{Value: "unquote-splicing"}
			value, err := quasiquoteExpressionToObjectWithLevel(e.Value, env, level-1)
			if err != nil {
				return nil, err
			}
			return &obj.PairObject{
				Car: symbol,
				Cdr: &obj.PairObject{
					Car: value,
					Cdr: obj.EMPTY_LIST,
				},
			}, nil
		}
	case *ast.QuasiquoteExp:
		// Increase nesting level
		result, err := quasiquoteExpressionToObjectWithLevel(e.Value, env, level+1)
		if err != nil {
			return nil, err
		}
		// Wrap in quasiquote symbol
		symbol := &obj.SymbolObject{Value: "quasiquote"}
		return &obj.PairObject{
			Car: symbol,
			Cdr: &obj.PairObject{
				Car: result,
				Cdr: obj.EMPTY_LIST,
			},
		}, nil
	case *ast.ListExp:
		// Handle empty list as NULL_OBJECT
		if len(e.Exps) == 0 {
			return obj.EMPTY_LIST, nil
		}

		// Check if this is a dotted list with unquote-splicing
		// Process each element, handling unquote and unquote-splicing specially
		var resultElements []obj.Object

		for _, subExp := range e.Exps {
			switch subSubExp := subExp.(type) {
			case *ast.UnquoteSplicingExp:
				// Only unquote-splice if we're at the right nesting level
				if level == 0 {
					// Evaluate the unquote-spliced expression
					splicedObj, err := EvalExpression(subSubExp.Value, env)
					if err != nil {
						return nil, err
					}

					// The result should be a list - append its elements to our result
					if pair, ok := splicedObj.(*obj.PairObject); ok {
						// Convert the pair to a slice of elements
						elements := pairToSlice(pair)
						resultElements = append(resultElements, elements...)
					} else if splicedObj.Type() == obj.EMPTYLIST_OBJ {
						// Empty list - nothing to append
					} else {
						// Not a list - treat as a single element
						resultElements = append(resultElements, splicedObj)
					}
				} else {
					// Still nested - treat as regular list element
					elem, err := quasiquoteExpressionToObjectWithLevel(subExp, env, level-1)
					if err != nil {
						return nil, err
					}
					resultElements = append(resultElements, elem)
				}
			default:
				// Regular element - process with quasiquote rules
				elem, err := quasiquoteExpressionToObjectWithLevel(subExp, env, level)
				if err != nil {
					return nil, err
				}
				resultElements = append(resultElements, elem)
			}
		}

		// Convert the slice of elements back to a proper list
		return sliceToPair(resultElements), nil
	case *ast.VectorExp:
		// Process each element, handling unquote and unquote-splicing specially
		var resultElements []obj.Object

		for _, subExp := range e.Exps {
			switch subSubExp := subExp.(type) {
			case *ast.UnquoteSplicingExp:
				// Only unquote-splice if we're at the right nesting level
				if level == 0 {
					// Evaluate the unquote-spliced expression
					splicedObj, err := EvalExpression(subSubExp.Value, env)
					if err != nil {
						return nil, err
					}

					// The result should be a list - append its elements to our result
					if pair, ok := splicedObj.(*obj.PairObject); ok {
						// Convert the pair to a slice of elements
						elements := pairToSlice(pair)
						resultElements = append(resultElements, elements...)
					} else if splicedObj.Type() == obj.EMPTYLIST_OBJ {
						// Empty list - nothing to append
					} else {
						// Not a list - treat as a single element
						resultElements = append(resultElements, splicedObj)
					}
				} else {
					// Still nested - treat as regular vector element
					elem, err := quasiquoteExpressionToObjectWithLevel(subExp, env, level-1)
					if err != nil {
						return nil, err
					}
					resultElements = append(resultElements, elem)
				}
			default:
				// Regular element - process with quasiquote rules
				elem, err := quasiquoteExpressionToObjectWithLevel(subExp, env, level)
				if err != nil {
					return nil, err
				}
				resultElements = append(resultElements, elem)
			}
		}

		return &obj.VectorObject{Elements: resultElements}, nil
	default:
		return obj.NULL_OBJECT, nil
	}
}

// Helper function to convert a pair object to a slice of elements
func pairToSlice(pair *obj.PairObject) []obj.Object {
	var elements []obj.Object

	current := pair
	for {
		elements = append(elements, current.Car.(obj.Object))

		if cdr, ok := current.Cdr.(*obj.PairObject); ok {
			current = cdr
		} else if current.Cdr == obj.EMPTY_LIST {
			break
		} else {
			// Improper list - add the final cdr as an element
			elements = append(elements, current.Cdr.(obj.Object))
			break
		}
	}

	return elements
}

// Helper function to convert a slice of elements to a pair object
func sliceToPair(elements []obj.Object) obj.Object {
	if len(elements) == 0 {
		return obj.EMPTY_LIST
	}

	root := &obj.PairObject{}
	current := root

	for i, elem := range elements {
		current.Car = elem
		if i == len(elements)-1 {
			// Last element - set cdr to empty list
			current.Cdr = obj.EMPTY_LIST
		} else {
			// Not last element - create next pair
			next := &obj.PairObject{}
			current.Cdr = next
			current = next
		}
	}

	return root
}

func applyQuasiquote(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("quasiquote requires exactly one argument")
	}

	return quasiquoteExpressionToObject(args[0], env)
}

func applyUnquote(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("unquote requires exactly one argument")
	}

	return EvalExpression(args[0], env)
}

func applyUnquoteSplicing(args []ast.Exp, env *obj.Environment) (obj.Object, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("unquote-splicing requires exactly one argument")
	}

	return EvalExpression(args[0], env)
}
